Clean Code

Presenter Notes

The goal of clean code is to make your code easy to understand and easy to change.

Presenter Notes

Analogy: making a schedule using the .ics format

Presenter Notes

Analogy: making a schedule using the .ics format

  • hard to understand
  • hard to change

Presenter Notes

Analogy: making a schedule using a google calendar

Presenter Notes

Analogy: making a schedule using a google calendar

  • easy to understand
  • easy to change

Presenter Notes

You can make code that's hard to understand and hard to change…

Presenter Notes

Or you can make code that's easy to understand and easy to change.

Presenter Notes

Let's illustrate the principles of clean code [CC] by example.

Presenter Notes

First example:

w = x2 - x1

Presenter Notes

First example:

w = x2 - x1

  • What are w, x1 and x2?
    • What do they represent?
    • What are they used for?

Presenter Notes

First example:

w = x2 - x1

  • What are w, x1 and x2?
    • What do they represent?
    • What are they used for?
  • Does "w" stand for "weight", "window", "word", or is it just a symbol for a generic computation?

Presenter Notes

First example:

w = x2 - x1

  • What are w, x1 and x2?
    • What do they represent?
    • What are they used for?
  • Does "w" stand for "weight", "window", "word", or is it just a symbol for a generic computation?

width = x_right - x_left

  • This answers all of the previous questions!

Presenter Notes

CC1. Use meaningful names

Presenter Notes

[CC1] Give names that reveal the purpose of things.

1
width = x_right - x_left

Presenter Notes

Next:

1
width = x_right - x_left + 10

Presenter Notes

Next:

1
width = x_right - x_left + 10

Where does '10' come from?

What does it represent?

Can I change it?

Presenter Notes

Next:

1
width = x_right - x_left + 10

Where does '10' come from?

What does it represent?

Can I change it?

1
width = x_right - x_left + 2 * horizontal_margin

Presenter Notes

Next:

1
width = x_right - x_left + 10

Where does '10' come from?

What does it represent?

Can I change it?

1
width = x_right - x_left + 2 * horizontal_margin

[CC1] Replace magic numbers with named variables, parameters, or constants

Presenter Notes

[CC1] Replace magic numbers

1
2
def width_with_margins(x_right, x_left):
    return x_right - x_left + 10

Presenter Notes

[CC1] Replace magic numbers

1
2
def width_with_margins(x_right, x_left):
    return x_right - x_left + 10

…with named parameters

1
2
def width_with_margins(x_right, x_left, horizontal_margin=5):
    return x_right - x_left + 2 * horizontal_margin

Presenter Notes

[CC1] Replace magic numbers

1
2
def width_with_margins(x_right, x_left):
    return x_right - x_left + 10

…with named parameters

1
2
def width_with_margins(x_right, x_left, horizontal_margin=5):
    return x_right - x_left + 2 * horizontal_margin

[CC1] Replace magic numbers

1
width = width * 0.3937007874

Presenter Notes

[CC1] Replace magic numbers

1
2
def width_with_margins(x_right, x_left):
    return x_right - x_left + 10

…with named parameters

1
2
def width_with_margins(x_right, x_left, horizontal_margin=5):
    return x_right - x_left + 2 * horizontal_margin

[CC1] Replace magic numbers

1
width = width * 0.3937007874

…with named constants

1
width_inches = width_cm * INCHES_PER_CENTIMETER

Presenter Notes

Note: magic numbers are not necessarily numbers!

PURPLE_HEX = "#6A0DAD"

Presenter Notes

Example from a past lecture:

1
check_divisible(a, b)

Presenter Notes

Example from a past lecture:

1
check_divisible(a, b)

What do you think this does?

Presenter Notes

Example from a past lecture:

1
check_divisible(a, b)

What do you think this does?

  • [CC1] Function names should say what they do

Presenter Notes

The implementation:

1
2
3
def check_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)

Presenter Notes

The implementation:

1
2
3
def check_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)

Misleading name: I don't expect "something that checks" to print anything

Presenter Notes

A more accurate name:

1
2
3
def print_if_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)

Presenter Notes

A more accurate name:

1
2
3
def print_if_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)
  • [CC1] Function names should say what they do

  • [CC1] Function names should reveal side-effects (such as printing to the console)

Presenter Notes

Another function call:

1
remove(l, n)

What do you think this does?

Presenter Notes

Another function call:

1
remove(l, n)

What do you think this does? Ambiguous name:

  • Does it remove the element in l whose value is equal to n?
  • Or does it remove the element in l at index n?

Presenter Notes

An unambiguous name:

1
remove_list_element_at_index(l, i)

Presenter Notes

An unambiguous name:

1
remove_list_element_at_index(l, i)
  • [CC1] Choose unambiguous names
  • Clarity at the point of use is more important than brevity
  • Include all the words needed to avoid ambiguity from the perspective of someone calling the function
  • A general naming template: verb_keywords (the verb indicates what the function does, the keywords what parameters are expected)

Presenter Notes

Two function calls:

1
2
add_number(a , b)
add_list(c, d)

What do you think these do?

Presenter Notes

The implementation of the functions:

1
2
3
4
5
def add_number(a , b):
    return a + b

def add_list(l, e):
    l.append(e)

Presenter Notes

The implementation of the functions:

1
2
3
4
5
def add_number(a , b):
    return a + b

def add_list(l, e):
    l.append(e)

Confusing to use the same word "add" for the two functions:

  • in the first case, add calculates the addition
  • in the second case, add inserts an element
  • in the first case, add has no side effects, in the second, it does!

Presenter Notes

A way to remove the confusion:

1
2
3
4
5
def add_numbers(a , b):
    return a + b

def append_element_to_list(e, l):
    l.append(e)

Presenter Notes

A way to remove the confusion:

1
2
3
4
5
def add_numbers(a , b):
    return a + b

def append_element_to_list(e, l):
    l.append(e)
  • [CC1] Use different words for different concepts

Presenter Notes

Another function call:

1
distance(couple_1, couple_2)

What is this about?

Presenter Notes

Another function call:

1
distance(couple_1, couple_2)

What is this about? Unclear:

  • What do couple_1 and couple_2 represent?
  • A "couple" could stand for many things: a point in 2D geometry, an interval, a complex number, a rational number...

Presenter Notes

Another function call:

1
distance(couple_1, couple_2)

Problem: we are mixing two levels of description

  • Level 1: the distance between some objects
  • Level 2: the implementation of these objects as couples

Presenter Notes

Example levels of description

Presenter Notes

Example levels of description

see "Structure and Interpretation of Computer Programs" by Abelson & Sussman (specifically the notion of "abstraction barriers")

Presenter Notes

A way to remove the confusion:

1
distance(point_1, point_2)
  • [CC1] Use the appropriate level of description

Presenter Notes

A way to remove the confusion:

1
distance(point_1, point_2)
  • [CC1] Use the appropriate level of description
  • If your program uses more than one kind of distance (e.g. between points and between intervals), the name "distance" is not precise enough.
    • Note that the function name does not include the argument names, since the caller can choose any other names.

Presenter Notes

A way to specify which kind of distance is computed:

1
2
distance_between_points(point_1, point_2)
distance_between_intervals(interval_1, interval_2)
  • [CC1] Use the appropriate level of description

Presenter Notes

Let's revisit an earlier example:

1
2
3
def print_if_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)

Presenter Notes

Let's revisit an earlier example:

1
2
3
def print_if_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)

This function does two things:

  1. Calculating whether an integer is divisible by another
  2. Printing conditionally on the result

Presenter Notes

Let's revisit an earlier example:

1
2
3
def print_if_divisible(n, divisor):
    if (n % divisor == 0):
        print(n, ' is divisible by ', divisor)

This function does two things:

  1. Calculating whether an integer is divisible by another
  2. Printing conditionally on the result

These are two conceptually distinct operations. There is no good reason for them to be done in the same function.

Presenter Notes

A solution:

1
2
def is_divisible(n, divisor):
    return (n % divisor == 0)

Presenter Notes

A solution:

1
2
def is_divisible(n, divisor):
    return (n % divisor == 0)
  • [CC2] Functions should do one thing

  • This solution has the added benefit to remove side-effects from the function.

Presenter Notes

CC2. Create functions that do one thing

Presenter Notes

Example code from a past student

  • What does display_dots_sparsity_cong do?
  • Do you find it easy to understand?
  • How would you make it easier to understand?

Presenter Notes

Presenter Notes

Potential rewrite

  • Do you find it easier to understand?

Presenter Notes

CC2. Create functions that do one thing

  • A lot of programming is about chunking
  • Chunking means grouping elements together in a meaningful named chunk (e.g. with a function) that you can manipulate as one conceptual unit
  • These chunks help you reason about your program and control its intellectual complexity

Presenter Notes

CC3. [Wait for it]

Presenter Notes

Presenter Notes

Presenter Notes

  • What's wrong? Code duplication.

Presenter Notes

  • What's wrong? Code duplication.
  • Why is it wrong?

Presenter Notes

  • What's wrong? Code duplication.
  • Why is it wrong? It makes code hard to change.

Presenter Notes

CC3. DRY: Don't Repeat Yourself

Presenter Notes

  • How would you solve this?

Presenter Notes

Presenter Notes

Last principle of clean code…

Presenter Notes

CC4. Explain yourself in code, not comments

Presenter Notes

Example from a past student:

1
2
3
4
if shuffledtarg_dist[i][1] == 1: ### IF TARGET ###
    # [some code ...]
elif shuffledtarg_dist[i][1] == 0: ### IF DISTRACTOR ###
    # [some other code ...]

Presenter Notes

Example from a past student:

1
2
3
4
if shuffledtarg_dist[i][1] == 1: ### IF TARGET ###
    # [some code ...]
elif shuffledtarg_dist[i][1] == 0: ### IF DISTRACTOR ###
    # [some other code ...]
  • Why do we need such comments next to if and elif?

Presenter Notes

Example from a past student:

1
2
3
4
if shuffledtarg_dist[i][1] == 1: ### IF TARGET ###
    # [some code ...]
elif shuffledtarg_dist[i][1] == 0: ### IF DISTRACTOR ###
    # [some other code ...]
  • Why do we need such comments next to if and elif?
  • Good intentions, but bad approach
  • [CC4] Comments do not make up for bad code

Presenter Notes

A first solution:

1
2
3
4
if stimulus_type == STIMULUS_TYPE_TARGET:
    # [some code ...]
elif stimulus_type == STIMULUS_TYPE_DISTRACTOR:
    # [some other code ...]

Presenter Notes

A first solution:

1
2
3
4
if stimulus_type == STIMULUS_TYPE_TARGET:
    # [some code ...]
elif stimulus_type == STIMULUS_TYPE_DISTRACTOR:
    # [some other code ...]
  • [CC4] Clear and expressive code with few comments is superior to obscure code with lots of comments

Presenter Notes

A first solution:

1
2
3
4
if stimulus_type == STIMULUS_TYPE_TARGET:
    # [some code ...]
elif stimulus_type == STIMULUS_TYPE_DISTRACTOR:
    # [some other code ...]
  • [CC4] Clear and expressive code with few comments is superior to obscure code with lots of comments
  • Can we do even better?

Presenter Notes

An even better solution:

1
2
3
4
if is_target(stimulus):
    # [some code ...]
elif is_distractor(stimulus):
    # [some other code ...]
  • [CC4] Clear and expressive code with few comments is superior to obscure code with lots of comments
  • Does this need any comments?

Presenter Notes

Example from a past student:

1
2
3
4
def distance_points(couple1,couple2):
    """Fonction controllant la distance entre nos points
    pour notre ensemble de points aléatoires"""
    return math.sqrt((couple1[0]-couple2[0])**2+(couple1[1]-couple2[1])**2)

Presenter Notes

Example from a past student:

1
2
3
4
def distance_points(couple1,couple2):
    """Fonction controllant la distance entre nos points
    pour notre ensemble de points aléatoires"""
    return math.sqrt((couple1[0]-couple2[0])**2+(couple1[1]-couple2[1])**2)
  • Misleading comment. It does not accurately describe what the function does.

Presenter Notes

An alternative:

1
def distance_between_points(point_1, point_2)
  • Does this need any comments?

Presenter Notes

Example from a past student:

1
exp = design.Experiment("Task") #create and name new exp object

Presenter Notes

Example from a past student:

1
exp = design.Experiment("Task") #create and name new exp object
  • Redundant comment. This is just noise. Remove it.

Presenter Notes

Recap: Clean Code Principles

The goal is to make code easy to understand and easy to change.

  • CC1 Use meaningful names.

    Reveal purpose. Replace magic numbers. Say what functions do. Reveal/Avoid side-effects. Remove ambiguity. Use different words for different concepts. Use the appropriate level of description.

  • CC2 Create functions that do one thing.

  • CC3 DRY: Don't Repeat Yourself.

  • CC4 Explain yourself in code, not comments.

Presenter Notes

Recap: Clean Code Principles

The goal is to make code easy to understand and easy to change.

  • CC1 Use meaningful names.

    Reveal purpose. Replace magic numbers. Say what functions do. Reveal/Avoid side-effects. Remove ambiguity. Use different words for different concepts. Use the appropriate level of description.

  • CC2 Create functions that do one thing.

  • CC3 DRY: Don't Repeat Yourself.

  • CC4 Explain yourself in code, not comments.

link to exercises

Presenter Notes